package com.yumeng.framework.auth.shiro.config;

import com.yumeng.common.data.enums2.AuthFrame;
import com.yumeng.common.exception.YumengException;
import com.yumeng.framework.auth.config.BaseAuthConfiguration;
import com.yumeng.framework.auth.service.TokenService;
import com.yumeng.framework.auth.shiro.authc.AuthcStrategy;
import com.yumeng.framework.auth.shiro.authc.MyModularRealmAuthenticator;
import com.yumeng.framework.auth.shiro.data.CacheManagerConstants;
import com.yumeng.framework.auth.shiro.filter.cus.CustomFormAuthenticationFilter;
import com.yumeng.framework.auth.shiro.filter.cus.CustomLogoutFilter;
import com.yumeng.framework.auth.shiro.session.ShiroSessionDAO;
import jakarta.servlet.Filter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.Authenticator;
import org.apache.shiro.authc.pam.*;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.cache.MemoryConstrainedCacheManager;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.mgt.RememberMeManager;
import org.apache.shiro.mgt.SubjectFactory;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.session.mgt.AbstractSessionManager;
import org.apache.shiro.session.mgt.DefaultSessionManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.session.mgt.eis.SessionDAO;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.subject.SubjectContext;
import org.apache.shiro.web.filter.mgt.DefaultFilter;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.mgt.DefaultWebSessionStorageEvaluator;
import org.apache.shiro.web.mgt.DefaultWebSubjectFactory;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Lazy;

import java.time.Duration;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 *
 * @author wxd
 * @date 2025/9/28 19:59
 */
@Slf4j
public abstract class ShiroConfiguration extends BaseAuthConfiguration {

    @Value("${server.servlet.session.timeout}")
    private Duration sessionTimeout;

    @Override
    protected AuthFrame currAuthFrame() {
        return AuthFrame.SHIRO;
    }

    private CacheManager autoCacheManager;
    @Autowired(required = false)
    @Qualifier(CacheManagerConstants.SHIRO_CACHE_MANAGER_BEAN_NAME)
    public void setAutoCacheManager(CacheManager autoCacheManager) {
        this.autoCacheManager = autoCacheManager;
    }

    /**
     * Shiro过滤器工厂
     */
    public static final String SHIRO_FILTER_FACTORY_BEAN_NAME = "shiroFilterFactoryBean";
    /**
     * WebSecurityManager
     */
    public static final String WEB_SECURITY_BEAN_NAME = "defaultWebSecurityManager";

    //region abstract

    /** 自定义过滤器 */
    protected abstract LinkedHashMap<String, Filter> getFilterMap();
    LinkedHashMap<String, Filter> getFilterMapForJson(){
        return null;
    }

    /** 自定义Url过滤规则 */
    protected abstract LinkedHashMap<String,String> getFilterChainDefinitionMap();
    LinkedHashMap<String,String> getFilterChainDefinitionMapForJson(){
        return null;
    }

    /** 最后一级过滤器名称 */
    protected String getLastFilterName(){
        return DefaultFilter.authc.name();
    }
    /** 是否注入了aop模块 解决引入springboot-aop模块后，导致url No mapping 404问题 */
    protected boolean injectedAop(){
        return false;
    }

    /** 是否启用session */
    protected boolean enableSession(){
        return true;
    }

    /** 获取所有Realms */
    public abstract Collection<Realm> getRealms();

    protected AuthcStrategy getAuthcStrategy(){
        return AuthcStrategy.AtLeastOneSucc;
    }

    /** 获取rememberMeManager */
    protected RememberMeManager getRememberMeManager(){
        return null;
    }

    /** 获取sessionManager */
    protected SessionManager getSessionManager(){
        DefaultWebSessionManager manager = new DefaultWebSessionManager();
        manager.setSessionIdUrlRewritingEnabled(false);
        return manager;
    }

    /** 获取cacheManager */
    protected CacheManager getCacheManager(){
        return null;
    }

    /** 获取SessionDAO */
    protected SessionDAO getSessionDAO(){
        return null;
    }

    protected TokenService getTokenService(){
        return null;
    }

    //endregion

    /**
     * 创建ShiroFilterFactoryBean
     *
     * Shiro内置过滤器，可以实现权限相关的拦截器
     *    org.apache.shiro.web
     *
     *  认证过滤器：
     *      anon：无需认证即可访问，游客身份。
     *      authc：必须认证（登录）才能访问。
     *      authcBasic：需要通过 httpBasic 认证。
     *      user：不一定已通过认证，只要是曾经被 Shiro 记住过登录状态的用户就可以正常发起请求，比如 rememberMe。
     *
     *  授权过滤器:
     *      perms：必须拥有对某个资源的访问权限（授权）才能访问。
     *      role：必须拥有某个角色权限才能访问。
     *      port：请求的端口必须为指定值才可以访问。
     *      rest：请求必须是 RESTFUL，method 为 post、get、delete、put。
     *      ssl：必须是安全的 URL 请求，协议为 HTTPS。
     *
     * @param securityManager
     * @return
     */
    @Bean(name=SHIRO_FILTER_FACTORY_BEAN_NAME)
    public ShiroFilterFactoryBean shiroFilterFactoryBean(@Lazy @Qualifier(WEB_SECURITY_BEAN_NAME) DefaultWebSecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        //添加自定义过滤器
        Map<String, Filter> filterMap = getCustomFilterMap();
        shiroFilterFactoryBean.setFilters(filterMap);

        //配置自定义Url过滤规则
        Map<String,String> filterChainDefinitionMap = getCustomFilterChainDefinitionMap();
        //默认放行
        String[] defaultPermitAllUrls = defaultPermitAllUrls();
        for (String permUrl :  defaultPermitAllUrls) {
            filterChainDefinitionMap.put(permUrl, DefaultFilter.anon.name());
        }
        filterChainDefinitionMap.put("/**", getLastFilterName());
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        //在AccessControlFilter中设置通用url，各子过滤器共用。各子过滤器也可自定义设置。
        //设置登录URL
        String loginUrl = getLoginUrl();
        if (StringUtils.isNotBlank(loginUrl)){
            shiroFilterFactoryBean.setLoginUrl(loginUrl);
        }
        //设置未经授权URL
        String unauthorizedUrl = getUnauthorizedUrl();
        if (StringUtils.isNotBlank(unauthorizedUrl)){
            shiroFilterFactoryBean.setUnauthorizedUrl(unauthorizedUrl);
        }
        //设置成功URL
        String successUrl = getLoginSuccessUrl();
        if (StringUtils.isNotBlank(successUrl)){
            shiroFilterFactoryBean.setSuccessUrl(successUrl);
        }

        return shiroFilterFactoryBean;
    }

    /**
     * 创建SecurityManager
     *
     */
    @Bean(name=WEB_SECURITY_BEAN_NAME)
    public DefaultWebSecurityManager securityManager(@Autowired(required = false) @Qualifier(CacheManagerConstants.SHIRO_SESSION_DAO_BEAN_NAME) ShiroSessionDAO shiroSessionDAO){
        Collection<Realm> realms = getRealms();
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

        securityManager.setAuthenticator(authenticator());
        //securityManager.setAuthorizer();//暂时不需要替换

        //关联realm
        securityManager.setRealms(realms);
        //SubjectDAO
        DefaultSubjectDAO subDao = (DefaultSubjectDAO)securityManager.getSubjectDAO();
        DefaultWebSessionStorageEvaluator webEvaluator = (DefaultWebSessionStorageEvaluator) subDao.getSessionStorageEvaluator();
        webEvaluator.setSessionStorageEnabled(enableSession());
        //subject工厂
        securityManager.setSubjectFactory(subjectFactory());
        //记住我管理器
        RememberMeManager rememberMeManager = getRememberMeManager();
        if (rememberMeManager != null){
            securityManager.setRememberMeManager(rememberMeManager);
        }
        //session管理器
        SessionManager sessionManager = getSessionManager();
        if (sessionManager != null){
            securityManager.setSessionManager(sessionManager);
            //TODO 查看是否会进入DefaultWebSessionStorageEvaluator setSessionManager
            //webEvaluator.setSessionManager(sessionManager);
        }
        sessionManager = securityManager.getSessionManager();
        if (sessionManager instanceof DefaultWebSessionManager manager){
            manager.setSessionValidationSchedulerEnabled(enableSession());
        }
        if (sessionManager instanceof DefaultSessionManager manager){
            if (shiroSessionDAO != null){
                manager.setSessionDAO(shiroSessionDAO);
            }
        }
        if (sessionManager instanceof AbstractSessionManager manager){
            if (sessionTimeout != null) {
                manager.setGlobalSessionTimeout(sessionTimeout.toMillis());
            }
        }
        //缓存管理器 优先级：子类提供 》 自动注入 》 默认内存缓存
        CacheManager cacheManager = getCacheManager();
        if (cacheManager != null){
            securityManager.setCacheManager(cacheManager);
        }else {
            if (this.autoCacheManager != null){
                securityManager.setCacheManager(this.autoCacheManager);
            }else{
                securityManager.setCacheManager(new MemoryConstrainedCacheManager());
            }
        }

        if (securityManager.getRememberMeManager() != null){
            log.info("Shiro RememberMeManager is {}", securityManager.getRememberMeManager());
        }
        if (enableSession()){
            if (securityManager.getSessionManager() != null){
                log.info("Shiro SessionManager is {}", securityManager.getSessionManager());
            }
            if (sessionManager instanceof AbstractSessionManager){
                long timeOut = ((AbstractSessionManager) sessionManager).getGlobalSessionTimeout();
                log.info("Shiro Session 超时时间 【{}】 秒", Duration.ofMillis(timeOut).toSeconds());
            }
        }else{
            log.info("Shiro Session 状态：【禁用】");
        }

        if (securityManager.getCacheManager() != null){
            log.info("Shiro CacheManager is {}", securityManager.getCacheManager().toString());
        }

        log.info("Shiro AuthenticationStrategy is {}", ((ModularRealmAuthenticator)securityManager.getAuthenticator()).getAuthenticationStrategy().toString());

        return securityManager;
    }

    /**
     * 初始化 身份验证器 Authenticator
     * 便于配置策略
     * 解决realm抛出异常被AuthenticationException覆盖的情况
     * @return
     */
    @Bean
    protected Authenticator authenticator(){
        ModularRealmAuthenticator authenticator = new MyModularRealmAuthenticator();
        authenticator.setAuthenticationStrategy(authenticationStrategy());
        return authenticator;
    }

    @Bean
    protected AuthenticationStrategy authenticationStrategy(){
        AuthcStrategy strategy = getAuthcStrategy();
        AuthenticationStrategy authcStrategy = null;
        if (AuthcStrategy.AtLeastOneSucc.equals(strategy)){
            authcStrategy = new AtLeastOneSuccessfulStrategy();
        }else if (AuthcStrategy.AllSucc.equals(strategy)){
            authcStrategy = new AllSuccessfulStrategy();
        }else if (AuthcStrategy.FirstSucc.equals(strategy)){
            authcStrategy = new FirstSuccessfulStrategy();
            ((FirstSuccessfulStrategy)authcStrategy).setStopAfterFirstSuccess(true);
        }else{
            throw new YumengException("认证策略异常");
        }
        return authcStrategy;
    }

/*    private SubjectDAO subjectDAO(){
        DefaultWebSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultWebSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(enableSession());

        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        return subjectDAO;
    }*/

    protected SubjectFactory subjectFactory() {
        return new DefaultWebSubjectFactory(){
            @Override
            public Subject createSubject(SubjectContext context) {
                context.setSessionCreationEnabled(enableSession());
                return super.createSubject(context);
            }
        };
    }

    /**
     * 获取过滤器
     * @return
     */
    private Map<String, Filter> getCustomFilterMap(){
        Map<String, Filter> filterMap = getFilterMap();
        if(filterMap == null){
            filterMap = new LinkedHashMap<>();
        }
        Map<String, Filter> filterMapForJson = getFilterMapForJson();
        if (MapUtils.isNotEmpty(filterMapForJson)){
            filterMap.putAll(filterMapForJson);
        }

        CustomFormAuthenticationFilter authcFilter = new CustomFormAuthenticationFilter();
        authcFilter.setUsernameParam(yumengAuthProperties.getBasic().getUsernameParameter());
        authcFilter.setPasswordParam(yumengAuthProperties.getBasic().getPasswordParameter());
        authcFilter.setRememberMeParam(yumengAuthProperties.getBasic().getRememberMeParameter());
        authcFilter.setClientTypeParameter(yumengAuthProperties.getBasic().getClientTypeParameter());
        filterMap.put(DefaultFilter.authc.name(), authcFilter);
        CustomLogoutFilter logoutFilter = new CustomLogoutFilter(getTokenService(), isFBS());
        logoutFilter.setRedirectUrl(getLoginUrl());
        filterMap.put(DefaultFilter.logout.name(), logoutFilter);

        return filterMap;
    }

    /**
     * 获取自定义过滤规则
     *
     * @return
     */
    private LinkedHashMap<String,String> getCustomFilterChainDefinitionMap(){
        LinkedHashMap<String,String> filterChainDefinitionMap = getFilterChainDefinitionMap();
        if (filterChainDefinitionMap == null){
            filterChainDefinitionMap = new LinkedHashMap<>();
        }

        LinkedHashMap<String,String> filterChainDefinitionMapForJson = getFilterChainDefinitionMapForJson();
        if (MapUtils.isNotEmpty(filterChainDefinitionMapForJson)){
            filterChainDefinitionMap.putAll(filterChainDefinitionMapForJson);
        }

        //注销
        filterChainDefinitionMap.put(getLogoutProcessingUrl(), DefaultFilter.logout.name());
        return filterChainDefinitionMap;
    }
}
